package com.sinevil.tcpTrans.utils.fileTrans;

import com.sinevil.tcpTrans.entity.json.FilePacketJson;
import com.sinevil.tcpTrans.entity.json.InstructionJson;
import com.sinevil.tcpTrans.interfaces.DownloadTaskListener;
import com.sinevil.tcpTrans.tcp.TCPTransNode;
import com.sinevil.tcpTrans.utils.FileUtil;
import com.sinevil.tcpTrans.utils.fileTrans.entity.FilePacketRequestInfo;
import com.sinevil.tcpTrans.utils.fileTrans.fileDownload.FileDownloadManager;
import com.sinevil.tcpTrans.utils.fileTrans.fileUpload.FileUploadManager;
import com.sinevil.tcpTrans.utils.log.FileTransLogger;
import lombok.SneakyThrows;
import org.slf4j.Logger;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * @Classname FileTranser
 * @Description 文件传输工具
 * @Version 1.0.0
 * @Date 2025/1/6 17:42
 * @Created by sinevil
 */
public class FileTranser {
    public static final int MB = 1048576;
    public static final int RECEIVE_BUFFER_SIZE = 10*MB;
    /**
     * 数据包大小
     * 1KB
     */
    public static final int PACKET_SIZE = 1024;
    /**
     * 切片大小
     * 切片大小最好为数据包的整数倍
     * 16MB
     */
    public static final int SLICE_SIZE = PACKET_SIZE*1024*16;
    /**
     * 同时最多存在请求数
     */
    public static final int MAX_REQUEST_NUM = RECEIVE_BUFFER_SIZE/PACKET_SIZE;
    /**
     * 最多同时执行任务数
     * 上传和下载分开计算
     */
    public static final int MAX_TASK_NUM = 5;
    /**
     * 最多重试次数
     */
    public static final int MAX_RETRY_TIME = 5;
    /**
     * 数据包请求超时重试时间
     */
    public static final int TIME_OUT = 5000;
    // MD5-(sliceCount * SLICE_SIZE).temp
    public static final String SLICE_MD5_FORMAT = "%s-%d";

    public static final Base64.Encoder Base64Encoder = Base64.getEncoder();
    public static final Base64.Decoder Base64Decoder = Base64.getDecoder();

    public final Logger logger = FileTransLogger.getLogger();
    /**
     * 下载保存文件路径
     */
    protected final Path downloadPath;
    /**
     * 下载临时文件位置
     */
    protected final Path downloadTempPath;
    protected final TCPTransNode transNode;
    /**
     * 上传请求管理器
     */
    public final FileUploadManager fileUploadManager;
    /**
     * 下载请求管理器
     */
    public final FileDownloadManager fileDownloadManager;

    /**
     * 若传入downloadPath为null则使用默认下载地址
     * @param downloadPath 下载地址
     * @param transNode csjd
     */
    public FileTranser(Path downloadPath, TCPTransNode transNode) throws NoSuchAlgorithmException {
        if (downloadPath == null){
            Path fileDownloadLocation = null;
             // 获取当前项目运行路径
            String currentDir = System.getProperty("user.dir");
            // 获取父目录路径（例如提取到 S:\Code\Java\project\OpenNetdisk）
            Path parentPath = Paths.get(currentDir).getParent();
            if (parentPath == null) {
                // 默认下载路径
                String userProfile = System.getenv("USERPROFILE");
                fileDownloadLocation = Path.of(userProfile + "\\Downloads");
            }else {
                fileDownloadLocation = parentPath.resolve("download");
            }
//            String userProfile = System.getenv("USERPROFILE");
//            fileDownloadLocation = Path.of(userProfile + "\\Downloads");
            downloadPath = fileDownloadLocation;
        }

        this.downloadPath = downloadPath;
        this.downloadTempPath = this.downloadPath.resolve("temp");
        // 确保下载路径及其临时路径存在
        FileUtil.ensureDirectoryExists(this.downloadPath);
        FileUtil.ensureDirectoryExists(this.downloadTempPath);

        this.transNode = transNode;

        this.fileUploadManager = new FileUploadManager(transNode);
        this.fileDownloadManager = new FileDownloadManager(transNode,this.downloadPath,this.downloadTempPath);
    }

    /**
     * 为下载任务设置监听器
     * 当下载任务执行对应流程时会触发监听器
     * @param listener 监听器
     */
    public void setDownloadListener(DownloadTaskListener listener) {
        this.fileDownloadManager.setListener(listener);
    }


    /**
     * 添加下载任务
     * @param name 文件名
     * @param uid 此次下载任务唯一标识
     * @param fileSize 文件大小
     * @param socketChannel 对应套接字
     * @return 是否添加成功
     */
    public boolean addDownloadTask(SocketChannel socketChannel, String name, String uid, long fileSize, String path) {
        // 返回 添加下载任务是否成功
        return fileDownloadManager.addNewDownloadTask(socketChannel,name,uid,fileSize,path);
    }

    /**
     * 收到 文件数据包
     * @param filePacketJson 文件数据包
     */
    @SneakyThrows
    public void acceptFilePacket(SocketChannel socketChannel,  FilePacketJson filePacketJson){
        if(!fileDownloadManager.acceptFilePacket(filePacketJson)) {
            // 如果添加数据包失败，说明不存在对应下载任务，上传端结束对应上传任务
            String str = String.format(InstructionJson.PUBLIC_REQUEST_FILE_COMPLETE_FORMAT, filePacketJson.uid, 2, "远程主机："+socketChannel.getLocalAddress() + " 不存在对应下载任务");
            this.transNode.sendMessage(socketChannel, new InstructionJson(InstructionJson.PUBLIC_REQUEST_FILE_COMPLETE, str));
        }
    }

    /**
     * 添加待下载任务，主要由客户端调用
     * @param socketChannel 服务端套接字
     * @param path 请求文件路径
     */
    public void downloadFile(SocketChannel socketChannel,String path){
        fileDownloadManager.downloadFile(socketChannel,path);
    }

    /**
     * 取消下载任务
     * @param socketChannel -套接字
     * @param path 文件路径
     */
    public void cancelDownload(SocketChannel socketChannel,String path){
        fileDownloadManager.cancelDownloadTask(path);
    }


    /**
     * 添加上传任务
     * @param path 即将上传的文件地址
     * @param socketChannel 远程套接字，用于返回信息
     */
    public void addUploadTask(String path, SocketChannel socketChannel) throws FileNotFoundException {
//        System.out.println("服务端 收到 客户端请求上传文件: " + path);
        fileUploadManager.addUploadTask(socketChannel,path);

    }

    /**
     * 主动请求上传文件
     * 一般由客户端使用
     * @param socketChannel 套接字
     * @param path 上传文件地址
     */
    public void addPendingUploadTask(SocketChannel socketChannel, String path){
        fileUploadManager.addPendingUploadTask(socketChannel,path);
    }

    /**
     * 添加上传数据包请求
     * 客户端或服务端请求数据包
     * @param uid 上传任务唯一标识
     * @param offset 偏移值
     * @param size 请求数据大小
     */
    public void addUploadPacketRequest(SocketChannel socketChannel, String uid,long offset,int size){

        if(!fileUploadManager.addUploadRequest(socketChannel, new FilePacketRequestInfo(uid,offset,size))) {
            // 如果添加数据包请求失败，说明不存在对应上传任务，告诉对方任务失败
            this.transNode.sendMessage(socketChannel,new InstructionJson(InstructionJson.PUBLIC_UPLOAD_FAIL,"UID("+uid+")"));
        }
    }

    /**
     * 完成文件上传
     * 不一定是正常完成，也可能是任务取消，或传输异常
     * @param uid 上传任务唯一标识
     * @param mark 传输状态 1表示返回文件MD5，2表示任务完成回收资源
     * @param content 传输信息
     */
    @SneakyThrows
    public void completeUploadTrans(SocketChannel socketChannel, String uid, int mark, String content){
        if (mark == 1) {
            String MD5 = getUploadMD5(uid);
            // 如果上传端获取到文件MD5，则返回文件MD5
            if(MD5 != null) {
                this.transNode.sendMessage(socketChannel, new InstructionJson(InstructionJson.PUBLIC_FILE_MD5, "UID(" + uid + "),MD5(" + MD5 + ")"));
                System.out.println("上传端发送MD5：" + MD5);
            }
            // 否则，告诉对方任务失败
            else
                this.transNode.sendMessage(socketChannel,new InstructionJson(InstructionJson.PUBLIC_UPLOAD_FAIL,"UID("+uid+")"));
        }
        else if (mark == 2) {
            // 清理上传任务,回收资源
            this.fileUploadManager.completeUploadTask(uid);
        }
        // 远程地址 传输状态
        logger.info("{}  {}  {}",
                socketChannel.getRemoteAddress(),
                mark == 1 ? "返回MD5" : "任务结束",
                content);
    }


    /**
     * 从上传管理器获取获取文件MD5
     * @param uid 上传任务唯一标识
     */
    private String getUploadMD5(String uid) throws IOException {
        return this.fileUploadManager.getFileMD5(uid);
    }

    /**
     * 接收上传端返回的文件MD5
     * @param socketChannel 上传端套接字
     * @param uid 任务唯一标识
     * @param MD5 文件MD5
     */
    public void acceptUploadMD5(SocketChannel socketChannel,String uid,String MD5){
        fileDownloadManager.acceptFileMD5(uid,MD5);
    }





}